#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import os
import sys

from ctools.common.ifilter import *
import ctools.common.treenode as treenode

from abc import abstractmethod, ABC

import logging
from ctools.logext import *

_fpath = os.path.dirname(os.path.realpath(__file__))
_rlog, _plog, _mlog, _tlog = get_loggers(__file__, __name__, propagate=False)

class SNode(treenode.TNode):
    def __init__(self, name='', direction='', data=None, parent=None):
        super().__init__(name, data, parent)
        self._direction = direction

    @property
    def direction(self):
        return self._direction

    def dump(self, pad='|   '):
        def func(node, args, depth):
            # print(node)
            prefix = args['pad'] * depth
            symbol = node.name.split(':')[-1]
            print(prefix + '+-- ' + symbol)
        args = {'pad': pad}
        self.iter_nodes(func, args)

class CallerNode(SNode):
    pass

class CalleeNode(SNode):
    pass

class TreeProvider(ABC, object):
    def __init__(self, ifilter:iFilter=None):
        self._filter = ifilter

    def set_filter(self, ifilter:iFilter):
        self._filter = ifilter

    def get_filter(self):
        return self._filter

    @abstractmethod
    def _get_items(self, pattern, direction, unique=False) -> list|tuple:
        if '>' in direction:
            return []
        else:
            return []

    def _filter_item(self, itemname:str):
        if self._filter:
            return self._filter.filter_item(itemname)
        return True

    def _get_tree(self, curr:SNode, end, level, direction, unique) -> SNode:
        def __check_node(_node:SNode, _end:str):
            if _end:
                for node in _node[:]:       # notice:
                    if node.name == _end:   # end node
                        continue
                    if len(node) > 0:       # end node is in subnodes
                        continue

                    # this is last node at this time, but not match end
                    _node.rm_item(node)
                    del node

                if len(_node) == 0:          # not end and no subnodes
                    return None
            return _node

        items = self._get_items(curr.name, direction, unique) if level else []

        for item in items:
            if not self._filter_item(item.name):
                _mlog.debug(f'skip item: {item}')
                continue
            _mlog.debug(f'add item: {item}')
            node = self._get_tree(item, end, level-1, direction, unique)
            curr.add_child(node)

        return __check_node(curr, end)

    def get_tree(self, *, start='', end='', level=1, direction='->', unique=False) -> SNode:
        '''
        must use start=xxx and/or end=xxx
        level: >=0, avoid infinite recursion
        direction: work if both start and end specified

              '->'             '<-'
         | sym11         | sym11
         |    sym21      | sym12          ^
         |        sym31  |     sym21      |
         |        sym32  | sym13          |
         v    sym22      |     sym22      |
                  sym33  |         sym31  |
        '''

        if level < 0:   # avoid infinite recursion
            raise ValueError('level must be >= 0')

        if start and end:
            pass
        elif start :
            direction = '->'
        elif end:
            direction = '<-'
        else:
            raise ValueError('start or end must be specified at least one')

        if '>' in direction:
            rootnode = SNode(start, direction)
            return self._get_tree(rootnode, end, level, direction, unique)
        else:
            rootnode = SNode(end, direction)
            return self._get_tree(rootnode, start, level, direction, unique)
